#include "fuente.h"
#include "nodoarbol.h"
#include <QVariant>
#include <QColor>
#include <QDebug>

#include <QDebug>



Fuente::Fuente(QImage i)
{
    image = i;
    cargarProbabilidades();
    extOrden = 1;
}

Fuente::Fuente()
{
}

void Fuente::setImage(QImage i)
{
    image = i;
}

int Fuente::getColor(int x, int y){
    QColor color = QColor::fromRgb(image.pixel(x,y));
    return color.blue();
}

int Fuente::getSizeProba(){
    return proba->size();
}

NodoArbol Fuente::huffmanTree(QList<NodoArbol> *l)
{
    //ordeno la lista de probabilidades, por su probalidad.
    QList <NodoArbol>* lista = ordenarLista(l);
    //obtengo el indice del ultimo valor
    //int numero = (lista->size()-1);

    if(lista->size()>1){

        //obtenemos el ultimo elemento de la lista
        NodoArbol aux= lista->takeLast();
        //eliminamos el ultimo elemento de lista
        //lista->removeAt(numero);
        //numero--;
        // creamos un nodo de arbol con el codigo y la probabilidad
        NodoArbol nodoNuevo(QVariant(-1).toString(),aux.getValor());
        //seteamos el nodo derecho al menor nodo
        nodoNuevo.setNodoDer(&aux);
        //obtenemso el anteultimo numero
        NodoArbol aux2=lista->takeLast();
        //eliminamos el anteultimo numero
        //lista->removeAt(numero);
        //numero--;
        //seteamos el nodo izquierdo
        nodoNuevo.setNodoIzq(&aux2);
        //sumamos la probabilidad de los dos ultimos y agregamos al final
        nodoNuevo.setProba(nodoNuevo.getValor()+ aux2.getValor());
        lista->push_back(nodoNuevo);
        return huffmanTree(lista);
    }
    else{
        QList<NodoArbol> *nueva = new QList<NodoArbol>();
        codeGenerate(&lista->first(), nueva, 0);
        return lista->first();
    }
}

void Fuente::codeGenerate(NodoArbol* r, QList <NodoArbol>*l,int num)
{
    if (!r->esHoja()){
        l->push_back(NodoArbol(0));
        codeGenerate(r->getDer(), l,num);
        l->pop_back();
        l->push_back(NodoArbol(1));
        codeGenerate(r->getIzq(),l,num);
        l->pop_back();
        }
        //ahora tenemos que ver, en que devolver el simbolo, con su codigo asociado.
        //esperemos  que ande si algun error seguramente este por aca..
    else
    {   NodoArbol aux=l->first();
        aux.setSimbolo(r->getSimbolo());
        l->replace(0,aux);
        QVector<NodoArbol> vector = l->toVector();
        QList<NodoArbol> lista = vector.toList();
        this->codigo.push_back(lista);
    }
}


QList<NodoArbol> Fuente::getCodigo(QString simbolo)
{
    for (int i=0; i<this->codigo.size(); i++){
        QList<NodoArbol> l = this->codigo.at(i);
        QString s= l.first().getSimbolo();
        if (s==simbolo)
             return l;
    }
}

//ver
QList<NodoArbol>* Fuente::ordenarLista(QList<NodoArbol>* orden)
{
    //defino la lista a devolver
    QList <NodoArbol>* list=new QList <NodoArbol>();
    int max=0;
    int tam = orden->size();
    for (int i=0; i<tam ;i++)
    {
        max=0;

        for (int j=1; j<orden->size();j++){
            //NodoArbol aux =orden->value(j);

            if (orden->value(j).getValor() > orden->value(max).getValor())
                max=j;
        }
        list->append(orden->takeAt(max));
    }


    return list;
}



void Fuente::codecSource(char* p)
{
        fileOpen(p);
        int buffer = 0;
        int cant_digitos = 0;
        QString simbolo;
        //generarLista()
        huffman(); // Hace todo lo relacionado con huffman
        //esto tendriamos que ver que onda como armar el simbolo
        int n = (image.width() * image.height())/extOrden;
        int x=0;
        int y=0;
        for (int i=0; i< n; i++){
            QList<int> lista;
            for (int j = 0; j < extOrden; j++){
                lista.insert(j,getSigPixel(x,y));//lista es una lista de enteros
            }
             simbolo= getSimbolo(lista); //simbolo... habria que hacer algo que devuelva el
            //esta fucnion ya existe... dado un simbolo me devuelve la lista con el codigo

            QList<NodoArbol> l = this->getCodigo(simbolo);

            //esto recorre la lista de codigo y genera el codigo en binario
            for (int i=0; i<l.size(); i++){
                buffer=buffer << 1;
                if (l.value(i).getValor()==1)
                        buffer=buffer | 1;
                else
                        cant_digitos ++;
                if (cant_digitos == 8){ // si la cantidad de digitos llega al tope de un char (8 bits)
                        writeFile(buffer);
                        buffer=0;
                        cant_digitos=0;
                }
            }
        }
        if ((cant_digitos <16) && (cant_digitos !=0)){
                buffer=buffer << (16-cant_digitos);
                writeFile(buffer);
        }
        fileClose();
}


void Fuente::huffman()
{
    QList<NodoArbol>* r = this->proba;
    NodoArbol* raiz= &this->huffmanTree(r);
    int numero=0;
 //   this->codeGenerate(raiz,codigo,numero);
}



QImage Fuente::getFuente()
{
    QImage fuente1(this->image);
    return fuente1;
}

// Calcula la entropia H(S) para una fuente sin memoria
//La lista p generalmente será "proba"
double Fuente::entropia() {

    double h = 0;
    double valor;
    for (int i=0;i< CANT_COLORES; i++) {
      valor = probColor[i];
	if (valor != 0)
	    h -= valor * log2(valor);
    }
    return h;
}

// Calcula la entropia H1(S) para una fuente con memoria 1.
double Fuente::entropia1() {
    double h = 0;

    for (int i=0;i<CANT_COLORES;i++)
	h += this->probColor[i] * entropiaMAT(i);
    return h;
}

//funcion auxiliar. Calcula la entropia usando una columna de la matriz de markov
double Fuente::entropiaMAT(int columna) {

    double h = 0;
    double valor;
    for (int i=0;i< CANT_COLORES; i++) {
      valor = markov[i][columna];
	if (valor != 0)
	    h -= valor * log2(valor);
    }
    return h;


}

QList<NodoArbol> *Fuente::clonar(QList<NodoArbol> *l){
    QList<NodoArbol> *clon = new QList<NodoArbol>();
    for(int i = 0; i < l->size(); i++){
        NodoArbol aux(l->value(i).getSimbolo(), l->value(i).getValor());
        clon->append(aux);
    }
    return clon;
}

void Fuente::calcularProbabilidades(double freColor[], int freMarkov[][256], double total) {
    QString simbolo;
    QList<NodoArbol> *lista = new QList<NodoArbol>();
    for(int i = 0; i <= 255; i++) {
        probColor[i] = freColor[i]/total;
        if (freColor[i] != 0){
            NodoArbol nodo(simbolo.setNum(i),freColor[i]/total);
            lista->append(nodo);
        }
    }
    matMarkov(freMarkov);

    proba = lista;
    colores = clonar(lista);
    proba = lista;
}

void Fuente::cargarFrecuencias(double freColor[], int freMarkov[][CANT_COLORES], int alto, int ancho){
    int id_color = getColor(0,0);
    freColor[id_color]++;
    for(int i = 0; i < alto; i++){
        for(int j = 0; j< ancho; j++)
            if (!((i == 0) && (j == 0))){
                int id_anterior = id_color;
		id_color = getColor(j,i);
                freColor[id_color]++;
		freMarkov[id_color][id_anterior]++;
            }
    }
}


void Fuente::cargarProbabilidades(){
    double freColor[CANT_COLORES];
    int freMarkov[CANT_COLORES][CANT_COLORES];
    for(int i = 0; i < 256; i++){
        freColor[i] = 0;
        for(int j = 0; j < 256; j++)
            freMarkov[i][j] = 0;
    }
    int alto = image.height();
    int ancho = image.width();
    cargarFrecuencias(freColor, freMarkov, alto, ancho);
    calcularProbabilidades(freColor, freMarkov, alto*ancho);    
}

void Fuente::matMarkov(int freMarkov[CANT_COLORES][CANT_COLORES]) {

    int sum;
    for (int j=0;j<CANT_COLORES;j++) {
	sum = 0;
	for (int i=0;i<CANT_COLORES;i++)
	    sum += freMarkov[i][j];
	for (int k=0;k<CANT_COLORES;k++)
	    if (sum != 0)
		markov[k][j] = freMarkov[k][j] / ((double) sum);
   }

}


//Extiende la fuente a un orden dado
void Fuente::extender(int orden) {

    extOrden = orden;
    //cargarProbabilidades();
    for(int n = 1; n < orden; n++){
        int largoInicial = proba->size();
        for(int i = 0; i < largoInicial; i++){
            QString sActual = proba->first().getSimbolo();
            QString ultimo = sActual.split(".").last();
            int ult = ultimo.toInt();
            double probActual = proba->first().getValor();
            proba->removeFirst();
            for(int j = 0; j < colores->size(); j++){
                QString sNuevo = (sActual + "." + colores->value(j).getSimbolo());
                //sNuevo.setNum(j);
                //sNuevo.insert(0, sActual + ".");
                NodoArbol aux(sNuevo, probActual * markov[ult][j]);
                proba->append(aux);
            }
        }
    }
}

int Fuente::getSigPixel(int &x, int &y){
    int color = getColor(x, y);
    if (x < (image.width()-1))
        x++;
    else
        x = 0;
    if (y < (image.height()-1))
        y++;
    else
        y = 0;
    return color;
}

void Fuente::getSim(QList<bool>* list,NodoArbol* ah,int &s)
{
    if ((ah->getSimbolo()=="-1")){
           bool aux=list->takeFirst();
           if (aux==0)
                getSim(list,ah->getDer(),s);
            else
                getSim(list,ah->getIzq(),s);
        }
     else
             s=ah->getSimbolo().toInt();



}

void Fuente::decodec(char* ch)
{   QImage aux(image.width(),image.height(),image.format());
    imagen2 =aux;
    QList<bool> *list=new QList<bool>();
    fileOpen(ch);
    int x = 0;
    int y = 0;
    while(!feof(archivo)){

        int caract = fgetc(archivo);
        int mascara = 1 << 7;
        for(int i = 0; i< 8; i++){
            if((caract & mascara)==128) //si el 1o bit de num es 1
              list->append(1);
            else
              list->append(0);
          }

         NodoArbol* raiz = &this->huffmanTree(colores);
         int s;
         while(!list->empty()){
            getSim(list,raiz,s);
            putPixel(x,y,s);
        }
}
}

QImage Fuente::getImagen()
{
    return this->imagen2;
}
   /* QList<NodoArbol>* list;
    fileOpen(ch);
    while(!feof(archivo))
    {
          int caract = fgetc(archivo);
          int mascara = 1 << 7;/
          for(int i = 0; i<8; i++)

              if((caract & mascara)==16384) //si el 1o bit de num es 1
                  list->append(1);
              else
                 list->append(0);
               caract = (caract << 1);
               getSim(list,ah,s);//te da el simbolo dado un codigo
               if (s!=-1)


    }*/


void Fuente::putPixel(int &x,int &y,int s)
{
    imagen2.setPixel(x,y,s);
    if (x < (image.width()-1))
        x++;
    else
        x = 0;
    if (y < (image.height()-1))
        y++;
    else
        y = 0;
}


QString Fuente::getSimbolo(QList<int> l){
    QString simbolo;
    QString aux;
    simbolo.setNum(l.first());
    for(int i = 1; i < l.size(); i++){
        aux.setNum(l.value(i));
        simbolo.append("." + aux);
    }
    return simbolo;
}

//Retorna la probabilidad del símbolo i
double Fuente::getProb(int i) {
    NodoArbol na = proba->at(i);
    return na.getValor();
}

double Fuente::mediaLong() {

    this->huffman();
    int total = 0;
    for (int i=0; i<this->codigo.size();i++)
	total += this->codigo.at(i).size();

    return total / ((double) this->codigo.size());
}


//Retorna una lista con las probabilidades de cada símbolo.
QList<NodoArbol>* Fuente::getProbabilidades() {
    return this->proba;
}

void Fuente::writeFile(int ch)
{
        fputc(ch, archivo);
}

void Fuente::fileOpen(char *path)
{
        archivo = fopen(path,"wb");
}


void Fuente::fileClose()
{
        fclose(archivo);
}

void Fuente::guardarExt(char* path) {

    //limpiar lista
    int mugre = 0;
    for (int i=0; i<this->proba->size();i++)
	if ((this->proba->value(i)).getValor() == (double) 0) {
	    this->proba->removeAt(i);
	    mugre++;
	    qDebug() << "i " << i << "mugre = " << mugre;
	}


    ofstream archivo;
    archivo.open(path);
    for (int i=0;i<this->proba->size();i++)
	archivo << this->proba->value(i).getSimbolo().toStdString() << "  " << this->proba->value(i).getValor() << "\n";
    archivo.close();
}
